feat: say what wikven does about Lua, and stop guessing at it - #473
Merged
Conversation
Scribunto is ordinary equipment on a MediaWiki wiki, and wikven's two products disagreed about it silently. Measured on a bake of one page invoking one module, every one of these exited 0:
| Scribunto | module file | rendered | module in the export |
|---|---|---|---|
| listed, engine present | `Module:Example` | `Lua says hello` | no |
| not listed | `Module:Example` | `{{#invoke:Greet|hello}}` | no, the file was ignored entirely |
| not listed | `Module:Example.wikitext` | `{{#invoke:Greet|hello}}` | **yes, as `Module%3AGreet.html`** |
So three of four ways to have a Lua module produced a published site with braces where the page meant to say something, and one of them exported the module's own Lua source as a page. Nothing said a word.
## The answer
Scribunto is supported under the Docker image and not in the standalone binary. That is not a preference: static-php-cli, which builds the binary's PHP, offers no Lua extension among the hundred and thirty it supports, and Scribunto's other engine shells out to a `lua` program that a single executable has no way to ship. Checked upstream rather than assumed.
## Two refusals
`Scribunto` holds the rule, and `build.php` puts it before `clearOutputDirectory` so a site that is refused still has its last bake on disk.
* a site with `Module:` files and no `Scribunto` in `extensions` is refused, naming the files;
* a site that lists Scribunto where no engine can run it is refused, naming the product that cannot.
Verified by baking, not by reading. Modules without Scribunto now exit 1 with no output written. Scribunto with no engine was tested by mounting an empty ini over the image's `docker-php-ext-luasandbox.ini`, which is as close to the binary as this machine gets, and it exits 1 with the message about the binary. A site with Scribunto and an engine bakes as before; a site with no Lua at all is told nothing, which matters because that is most sites and the binary is what they are pointed at.
Engine detection is `luasandbox`, then a configured `luaPath`, then `lua5.1` or `lua` on PATH, so a hand-rolled install with the standalone engine is not refused for no reason. The image has no `lua` on PATH, which the test above also proves: the check fired.
## The page
`Lua modules`, in the docs section between Extensions and JavaScript, in English and Korean. It says how to enable it, that a module file carries no `.wikitext` marker because its content model is Lua, what reaches the published site, and what the two refusals look like. `Standalone binary` gains a "What it cannot do" section, since it claimed its output was the same as the image's, and that is now known to be false in one way. `Extensions` points at the new page.
The page demonstrates itself: it shows `Module:Example` in full, invokes it, and prints the answer. The module returns the title of the page it ran on and no prose, because a module's return value is not a translation unit and would reach a reader of any language in whatever language it was written in.
## What holds it
`smoke.yml` asserts the module's answer is in the exported page in both languages, and that nothing named `Module*` was exported. It cannot assert the absence of `{{#invoke:` anywhere, because the new page documents the syntax inside `<nowiki>` and so contains it legitimately -- the positive assertion is the one that distinguishes rendering from leaking.
Ran locally against a real bake of this site: both languages carry the answer, `<code>Lua modules</code>` and `<code>Lua modules/ko</code>`, the module is not a page, the language-bar count still matches what the source predicts (414), and two bakes are byte-identical, so a module does not cost reproducibility. The prevnext chain reads Skins, Extensions, Lua modules, JavaScript, Searching in English, and shows `Lua 모듈` in Korean.
Unit suite: `OK (196 tests, 299 assertions)` against main, eleven of them new.
## A consequence worth stating
This documentation site now has a Lua module, so the standalone binary cannot bake it. Nothing in CI notices, because nothing bakes anything with the binary -- `binary.yml` builds it and runs `./wikven version`. That gap is real and is not this change's to close.
Closes #465.
---
_Generated by [Claude Code](https://claude.ai/code/session_935f02d1)_
Co-authored-by: Claude <noreply@anthropic.com>
A source tree with Module: files and no Scribunto in extensions ended the build. That is deciding for the site: what those files are is a guess read off a name, and they may be on their way in, on their way out, or kept for something else. The bake now goes on and says what it sees -- the invocations stay in the pages as their own source text, and a module named with the .wikitext marker is exported as a page. The other rule stays fatal, because it is not the same thing. A site that lists Scribunto where no engine can run it asked for Lua in so many words, and this build cannot give it; publishing braces instead is worse than being told no. So Scribunto::problem() narrows to the engine case and a new warning() carries the modules case, and build.php's check is no longer only an assertion.
This was referenced Aug 19, 2026
lens0021
added a commit
that referenced
this pull request
Aug 20, 2026
… that found (#481) Nothing had ever baked anything with the standalone binary. `binary.yml` built it and ran `./wikven version` — which proves the static link, since static linking fails at execution rather than compilation, but proves nothing about the product — and `smoke.yml` only ever uses the Docker image. So a site the image can bake and the binary cannot had never been caught by anything, which is how this repository's own documentation came to hold a Lua module the binary was documented as unable to render. This adds the missing job, fixes the three bugs it found on its first runs, and rewrites the documentation those runs proved wrong. Closes #465's remaining gap; the follow-up #473 flagged. ## The job A `bake` job hanging off `binary` with `needs:`, so it runs exactly when a binary was built. Not folded into `smoke.yml`: that job belongs to the image, it is a required check, and Lua is a niche of a niche. It runs on a plain runner with the binary downloaded as an artifact — the way somebody who fetched a release meets it, with no Docker and nothing of wikven's installed. Three assertions, in this order, because the order is load-bearing: | | asserts | where | |---|---|---| | a bake with no `src` | a refused bake exits non-zero | both | | Scribunto's bundled lua, nothing configured, **before anything is installed** | the out-of-the-box claim | x86_64 | | `luaPath` at an interpreter the host provides | the arrangement that works anywhere | both | Putting the bundled-lua bake after the install step made it pass for the wrong reason on the first run: `/usr/bin/lua5.1` was on `PATH`, which is one of the things `luaEngineAvailable()` looks for, so the bake was never asked whether it can manage without one. A pull request builds x86_64 only; the arm64 leg runs on the nightly and on releases, which is where an arm binary exists at all. It was exercised for this branch on the throwaway #482. ## What it found **1. A relative `WIKVEN_WORKDIR` broke the skin pass.** The first bake ever run got through the import and the job queue and died: ``` --- vector-2022 pass failed (exit 1) --- DBQueryError ... Error 1: no such table: page Query: SELECT MIN(page_id) FROM "page" #9 .../extensions/Wikven/maintenance/build.php(842): RebuildFileCache->execute() ``` A skin pass is spawned with the MediaWiki root as its working directory — `proc_open(..., $GLOBALS['IP'], ...)`, because the binary's php-cli needs script paths relative to the install root — and inherits `WIKVEN_WORKDIR`. Inside the binary that root is the temporary directory the embedded app is unpacked into, so a relative value means one place in the orchestrator and another in every pass: the pass opened an empty database beside the unpacked app instead of the one just filled. Not a corner case — it is what `binary.Dockerfile`'s own usage note documents, `WIKVEN_WORKDIR=. ./wikven build`. Under Docker the workdir is `/workspace`, absolute, which is why it never showed up. Resolved with `realpath()`. **2. The engine check refused sites it could have baked.** `luaEngineAvailable()` looked for `luasandbox`, a configured `luaPath`, and `lua` on `PATH`, and never for the interpreter Scribunto carries in its own source tree — which is what `luastandalone` runs when `luaPath` is null, and what `luaautodetect` falls back to when `luasandbox` is absent. It looks for it now, and **runs** it rather than trusting `is_executable()`: `LuaStandaloneInterpreter` picks among the bundled binaries by `PHP_OS` and `PHP_INT_SIZE` and never by architecture, so on arm the file it selects is an x86-64 ELF that exists, is executable, and cannot start. Executing it is what makes mirroring that choice safe — a wrong guess answers no rather than promising Lua that never arrives. **3. Every failed build exited 0.** Caught by the assertion added here, on both architectures: ``` wikven: no source directory at /home/runner/work/wikven/wikven/empty/src a bake with no source directory exited 0 ``` `reexec()` returned the child's exit code with a nil error, meaning to propagate the status without Caddy printing its own line over the child's diagnostic. Caddy 2.11.4, `cmd/cobra.go`: ```go status, err := f(Flags{cmd.Flags()}) if status > 1 { cmd.SilenceErrors = true return &exitError{ExitCode: status, Err: err} } return err ``` A status of exactly 1 is never turned into an exit status, and an ordinary failed build is 1. So a Makefile, a CI job or a deploy script running `wikven build` read every refusal as a success — the Lua refusal, a missing source directory, a failed skin pass, all of them. `reexec()` exits instead of returning; returning `(1, err)` would exit 1 but print Caddy's error line over the diagnostic the child already wrote, which is what the original nil was avoiding. ## The documentation `Lua modules` and `Standalone binary` both said the binary cannot render Lua. Written before anything had baked with it, and wrong in two cases of three: | Product | Engine | What you configure | |---|---|---| | Docker image | `luasandbox`, compiled into its PHP | nothing | | Standalone binary, x86-64 | the interpreter Scribunto ships | nothing | | Standalone binary, arm64 | an interpreter you install | `luaPath` | Every row is measured rather than reasoned. `lua5.1 5.1.5-9build2` installs from `universe` on the arm runner, no `luajit` fallback needed, and the bake with `luaPath` pointed at it finished with the module's answer in the page. arm64 with nothing installed refuses cleanly — no output written, and now a non-zero status. `Standalone binary`'s "What it cannot do" becomes "What it asks of the host", which is what the section now describes. Korean follows, restamped. ## Not done here `binary` is still 504s and still off the critical path of nothing, which is #479's problem, not this one's. --------- Co-authored-by: Claude <noreply@anthropic.com>
lens0021
added a commit
that referenced
this pull request
Aug 20, 2026
Closes #455. ## The duplication The order of the documentation was written down twice: as `MediaWiki:Sidebar`, and again as the arguments of the `{{prevnext}}` at the foot of each of twenty-one pages. Nothing checked that the two agreed, and twice they did not — a page reached the sidebar with no call naming it, and the last page of the chain had no row at all, because a lone argument read as the *next* step and no positional form could spell "nothing after this". They agree today, as it happens: twenty sidebar entries, and the chain covers all twenty in the sidebar's order with `index` at the head. That is the state to keep, not a state to rely on — both past divergences were repaired by hand, and the shape that produced them was untouched. ## The change `Module:Sequence` reads the sidebar, so `{{prevnext}}` takes no arguments. Adding a page is now two edits instead of three, and the third was the invisible one. The direction is forced, as #455 worked out: `MediaWiki:Sidebar` is read line by line by the skin rather than parsed as wikitext, so it can call nothing; but it is an ordinary page, so anything can read it. What the sidebar contributes is **order alone**. Labels stay in `Template:prevnext/label`, which reads each target's own translated title — so none of this reaches the translations. A prevnext sits outside the `<translate>` tags, and no `ko.wikitext` changed in this branch. ## The two decisions #455 left open * **The group boundary.** The sequence keeps crossing from the docs group into the references group, as it does today. A reader who reaches the end of the first group is better sent on than stopped. * **`index`.** It heads the sequence without being a sidebar entry, so the module prepends it. The alternative — a sidebar entry for it — would duplicate what every skin's logo already points at, which is why the sidebar's own comment says it has none. ## The condition #455 set > **check first that it runs in the image**: Scribunto needs either the LuaSandbox PHP extension or a standalone `lua` binary, and neither is obviously present in the Alpine-based image. If it is missing, this proposal costs a Dockerfile change, and that changes the trade. It runs. The base image does `pecl install LuaSandbox-4.1.2`, and this site has rendered `Module:Example` since #473. **No Dockerfile change**, which is what tips this over the alternative — #455's cheaper option was a check that asserts the two copies agree, and a check that watches a duplication is worth less than not having the duplication, once the module is free. ## What holds it `smoke` gets what the old shape got wrong, stated as three assertions: every page the sidebar names has a navigation row, `index` has no previous link, and `Licenses` has no next. The existing check that `Searching/ko`'s row carries Translating's *Korean* title still stands and now also proves the module renders at all. Draft because the module cannot be run here — no Docker on this machine, so the first real execution is CI's bake. --- _Generated by [Claude Code](https://claude.ai/code/session_01Uv1RzRurUH6wrgV5E9PESQ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
lens0021
pushed a commit
that referenced
this pull request
Aug 31, 2026
🤖 I have created a release *beep* *boop* --- ## 1.0.0 (2026-08-31) The first release. There is no earlier version to compare against or upgrade from, so what follows is not a list of changes to one but the work this release is made of, as the commits recorded it. ### Features * **action:** default to a pinned image version and cache it between runs ([#188](#188)) ([f7fcb40](f7fcb40)) * **actions:** offer a guiding pull request comment on translation findings ([#520](#520)) ([1ab0175](1ab0175)) * add a "View source" tab linking to the repo source ([#119](#119)) ([24e4d16](24e4d16)) * add a {{WIKVENVERSION}} variable ([#604](#604)) ([a702b9f](a702b9f)) * add a footer skin switcher ([#114](#114)) ([1867ea6](1867ea6)) * add a mark command to number translation units ([#216](#216)) ([0fcde73](0fcde73)) * add a serve subcommand for local preview ([#155](#155)) ([8d97799](8d97799)) * add translate scaffold to generate translation skeletons ([#223](#223)) ([48ce2ab](48ce2ab)) * bake ULS webfonts into the static export (opt-in) ([#286](#286)) ([074a103](074a103)) * bake with the standalone binary in CI, and fix the three things that found ([#481](#481)) ([8cf3bc3](8cf3bc3)) * base the image on mediawiki:1.46-fpm-alpine ([#332](#332)) ([f3fdcee](f3fdcee)) * **build:** list the site's extensions and skins, with their licenses ([#523](#523)) ([42d75d2](42d75d2)) * **build:** name wikven in the User-Agent of every request it makes ([#542](#542)) ([7d6d1ce](7d6d1ce)) * bundle the native search closure so SifterSearch search works offline ([#98](#98)) ([cf6b910](cf6b910)), closes [#97](#97) * derive page files from content model, dropping the .css.wikitext suffix ([#62](#62)) ([ef645c4](ef645c4)) * derive the footer project link label from the repo host ([#118](#118)) ([e14c059](e14c059)) * **docs:** give the landing page symbols, an output it can show, and some warmth ([#524](#524)) ([3b07995](3b07995)) * **docs:** give the note template a teal icon ([#341](#341)) ([05a7ea2](05a7ea2)) * **docs:** keep the rendered page behind the tree instead of trading places ([#530](#530)) ([83fc9ee](83fc9ee)) * **docs:** put a copy button on the code blocks ([#377](#377)) ([3beee91](3beee91)) * **docs:** stop the sidebar linking to the page you are reading ([#499](#499)) ([1bf9e2b](1bf9e2b)) * **docs:** turn the diagram's output pane over on scroll where the browser can ([#526](#526)) ([6c7bcc8](6c7bcc8)) * fold the version page into an About page ([#426](#426)) ([af61267](af61267)) * gate translation checks on broken source pages, not on stale translations ([#385](#385)) ([aacfbfc](aacfbfc)) * generate a Special:Version-style page linked from the footer ([#117](#117)) ([d2dfa8a](d2dfa8a)) * give Minerva a main menu the export can serve ([#366](#366)) ([fc59117](fc59117)) * give the standalone binary the translate commands ([#507](#507)) ([25ba28e](25ba28e)) * **i18n:** translate the footer, skin switcher and version page strings ([#123](#123)) ([9849b36](9849b36)) * index each language separately, and assert it stays that way ([#447](#447)) ([b4b01cc](b4b01cc)) * keep non-main skin pages out of search indexes ([#113](#113)) ([0d8d94e](0d8d94e)) * keep Vector's appearance menu, which works statically ([#387](#387)) ([ce30fea](ce30fea)) * let a site choose the skin it is read in ([#589](#589)) ([5f96e9e](5f96e9e)) * let a skin author turn off the chrome wikven imposes ([#551](#551)) ([ea78cab](ea78cab)) * let a stamp mean someone read the translation ([#572](#572)) ([12d3292](12d3292)) * load .wikven.yaml through MediaWiki's settings system and validate it ([#77](#77)) ([0952b3b](0952b3b)) * localize translated-page chrome and route Special:Translate links to the edit host ([#229](#229)) ([3afa7c4](3afa7c4)) * make page titles translatable, and translate them into Korean ([#256](#256)) ([acdd975](acdd975)) * make the Translating page translatable ([#248](#248)) ([6122151](6122151)) * name a setting that belongs to no one ([#578](#578)) ([3df5b0e](3df5b0e)) * name the setting core cannot accept ([#574](#574)) ([56f2d80](56f2d80)) * offer the Citizen skin (search hidden) ([#115](#115)) ([b83717b](b83717b)) * pin and verify fetched extensions/skins ([#74](#74)) ([8ccd5e7](8ccd5e7)) * point non-main skin pages' canonical at the main copy ([#116](#116)) ([5bf1d6a](5bf1d6a)) * publish images on releases and nightlies, not on every commit to main ([#488](#488)) ([18fa018](18fa018)) * publish the image to quay.io as well, with no token to keep ([#485](#485)) ([f387e5e](f387e5e)) * put everything the build generates in one directory ([#584](#584)) ([d8b8f43](d8b8f43)) * put the generated templates under one name, and keep the old ones ([#559](#559)) ([2cd766a](2cd766a)) * put the pictures pages carry where the rest of the output goes ([#592](#592)) ([78fdf9f](78fdf9f)) * put the skin switcher in the toolbox and drop the footer select ([#351](#351)) ([2f2551e](2f2551e)) * render each enabled skin into its own output directory ([#111](#111)) ([b0adc94](b0adc94)) * require MediaWiki 1.46, dropping 1.45 support ([#236](#236)) ([993e62c](993e62c)) * resolve Special:MyLanguage/ links in the static export ([#225](#225)) ([f4904ea](f4904ea)) * say what every built site redistributes, on a page the footer links ([#560](#560)) ([3fe4eb6](3fe4eb6)) * say what wikven does about Lua, and stop guessing at it ([#473](#473)) ([ea10b7d](ea10b7d)) * select the build skin via WIKVEN_BUILD_SKIN ([#110](#110)) ([126a021](126a021)) * serve logos from a shared upload instead of inlining them per page ([#56](#56)) ([ad4017a](ad4017a)) * serve the search results at Search and exclude it from indexing ([#104](#104)) ([85d35e2](85d35e2)) * settle the actions' inputs before 1.0.0 freezes them ([#583](#583)) ([2bee0c1](2bee0c1)) * settle what a page is called in the output ([#605](#605)) ([a2860f7](a2860f7)) * ship a composite action and dogfood it in the docs deploy ([#150](#150)) ([19392b7](19392b7)) * ship SifterSearch as a built-in, on-by-default search extension ([#195](#195)) ([7b19be8](7b19be8)) * stop the build on three things it used to publish around ([#590](#590)) ([9c0a0b3](9c0a0b3)) * support Citizen in wikven instead of per-site workarounds ([#369](#369)) ([bced0dd](bced0dd)) * translate page content into other languages ([#214](#214)) ([28ff2b2](28ff2b2)) * use the core viewsource message for the View source tab ([#120](#120)) ([24230c0](24230c0)) ### Bugfixes * abort the build when a page fails to import ([#146](#146)) ([cf0a007](cf0a007)) * **action:** use a locally-built image instead of always pulling ([#194](#194)) ([8046aa4](8046aa4)) * answer symlinks where a source tree is read, not where a path is bounded ([#591](#591)) ([d3be13e](d3be13e)) * build the image when a Translate dev dependency has an advisory ([#277](#277)) ([00838b9](00838b9)) * build the search index once, at the end ([#283](#283)) ([09a5324](09a5324)) * **build:** put wikven in front of the User-Agent core builds for Commons ([#543](#543)) ([4c774a3](4c774a3)) * bump bundled SifterSearch to v0.6.0 (stop the double search box) ([#200](#200)) ([cd38aa7](cd38aa7)) * bundle the modules core loads by looking at the rendered page ([#484](#484)) ([c96c107](c96c107)) * **check-translations:** keep the comment to what the change touches ([#541](#541)) ([8288229](8288229)) * clear the output directory before each build ([#72](#72)) ([b4cf416](b4cf416)) * clear the title cache once the translation units exist ([#466](#466)) ([562ea9e](562ea9e)) * create directories with wfMkdirParents ([#289](#289)) ([5157174](5157174)) * date each page at the commit that changed it, not at the bake ([#424](#424)) ([4cd31f4](4cd31f4)), closes [#406](#406) * declare the build's directory settings in extension.json ([#262](#262)) ([65432b5](65432b5)) * decode JS-bundle URL escapes with json_decode in AssetLocalizer ([#297](#297)) ([ccbf702](ccbf702)) * **docs:** cross the output pane's two states gradually instead of at a line ([#540](#540)) ([85fb9c9](85fb9c9)) * **docs:** cut the hero's light at the screen, and turn the pane over mid-screen ([#531](#531)) ([ea482af](ea482af)) * **docs:** keep sidebar links in the reader's language ([#382](#382)) ([f2b27f4](f2b27f4)) * **docs:** keep the hero's light inside the hero ([#525](#525)) ([aaeef7a](aaeef7a)) * **docs:** keep the logo in the reader's language ([#391](#391)) ([749f507](749f507)) * **docs:** let the hero's light run off the frame so it reads as ground ([#529](#529)) ([3d40660](3d40660)) * **docs:** let the hero's light run past the text column again ([#539](#539)) ([2fbee0e](2fbee0e)) * **docs:** let the output pane settle back on the page as the diagram leaves ([#544](#544)) ([9d0785a](9d0785a)) * **docs:** light the hero from its corners, and drop the clip [#531](#531) needed ([#536](#536)) ([186421c](186421c)) * **docs:** make the brand teal and the note box legible in dark mode ([#489](#489)) ([a03a76c](a03a76c)) * **docs:** make the hero's light read as ground rather than as two spheres ([#528](#528)) ([e31f7a5](e31f7a5)) * **docs:** paint the landing page's marks on its translations, and its buttons anywhere ([#498](#498)) ([1ff3a50](1ff3a50)) * **docs:** stop the diagram clipping its own file names on a narrow screen ([#527](#527)) ([74c35fb](74c35fb)) * **docs:** translate the Minerva section of Skins into Korean ([#415](#415)) ([2ac133a](2ac133a)) * don't treat a documented <translate> example as a translation source ([#239](#239)) ([cdcc388](cdcc388)) * drain translation jobs by hand again, reverting [#311](#311) ([#326](#326)) ([4170fd7](4170fd7)) * drop dead footer/search affordances from the static export ([#160](#160)) ([c08a5d2](c08a5d2)) * drop the .git the bundled extension clones leave in the image ([#330](#330)) ([d19dbf4](d19dbf4)) * drop the category footer's dead Special:Categories link ([#173](#173)) ([2df6bf8](2df6bf8)) * drop the discussion tab in every skin, Minerva included ([#493](#493)) ([cd0fccd](cd0fccd)) * dump ResourceLoader modules without load.php's HTTP response ([#259](#259)) ([3a39aac](3a39aac)) * end a source translation unit where its <translate> block ends ([#379](#379)) ([50aebd8](50aebd8)) * export subpage titles as real subdirectories ([#213](#213)) ([1db8e0d](1db8e0d)) * fail on un-localized images and hand output back to the host user ([#161](#161)) ([1bb8c86](1bb8c86)) * fail the build when a stylesheet never reaches the disk ([#565](#565)) ([6ad1b41](6ad1b41)) * fail the build when the webfonts a site asked for cannot be copied ([#566](#566)) ([6fbd87a](6fbd87a)) * fail when a composer package lands where nothing loads it ([#581](#581)) ([263fee5](263fee5)) * fetch a third-party extension again when its pin moves ([#546](#546)) ([44938c2](44938c2)) * fetch the bundled extensions as tarballs, not clones ([#331](#331)) ([9f60efa](9f60efa)) * freeze page_touched so wiki-page modules hash the same every bake ([#271](#271)) ([853c06f](853c06f)) * give a fetch that reaches the network more than one go ([#469](#469)) ([ba9fb1d](ba9fb1d)) * give Citizen's "View source" tab an icon ([#440](#440)) ([f0b4e02](f0b4e02)) * give Citizen's search shortcuts the search the export has ([#397](#397)) ([27eeebf](27eeebf)) * give every source image its own File: page ([#603](#603)) ([623b866](623b866)) * give the per-skin build its own environment and working directory ([#298](#298)) ([cfb0243](cfb0243)) * give the skin list its own section instead of renaming the toolbox ([#363](#363)) ([aa13b7b](aa13b7b)) * grant the release binary job the provenance permissions it needs ([#177](#177)) ([be9f283](be9f283)) * hide dead skin chrome left by the static export in Timeless and Citizen ([#196](#196)) ([1b4c9e1](1b4c9e1)) * hide edit/history/source links on generated pages ([#130](#130)) ([fb6df1e](fb6df1e)) * hide the category footer on the static export ([#121](#121)) ([939c435](939c435)) * hide the empty tools box each skin draws around the toolbox ([#346](#346)) ([5afb909](5afb909)) * index a translated page once, not once per title it has ([#458](#458)) ([e934e00](e934e00)) * keep $wgCacheEpoch out of module versions ([#270](#270)) ([7795bf9](7795bf9)) * keep a search inside the skin copy the reader is in ([#434](#434)) ([32e7f6d](32e7f6d)) * keep a skin pass out of the other skins' pages ([#430](#430)) ([e84cd11](e84cd11)) * keep the build's chrome off a licenses page the site wrote ([#568](#568)) ([e45596e](e45596e)) * keep the paths the build derives out of a site's hands ([#573](#573)) ([e21b16b](e21b16b)) * keep the site's own styles linked from a style directory ([#563](#563)) ([5dfea21](5dfea21)) * leave a page of its own where a translation would go ([#597](#597)) ([09934b7](09934b7)) * link the search toggle to the results page, not to Special:Search ([#421](#421)) ([3b3b2d5](3b3b2d5)) * list directories with FilesystemIterator, not glob ([#294](#294)) ([f8188fa](f8188fa)) * make edit/history links correct for all valid titles ([950c0b2](950c0b2)), closes [#67](#67) * make storeImages downloads status-aware and stream to disk ([#307](#307)) ([b75afc3](b75afc3)) * make the exported HTML the same in every bake ([#282](#282)) ([caf6661](caf6661)) * make the first listed skin the default skin ([#109](#109)) ([19e3fae](19e3fae)) * make the main page configurable and fail if it was not imported ([#71](#71)) ([4399b8f](4399b8f)) * make verbatimRanges() comment-aware ([#312](#312)) ([59117f7](59117f7)) * match <translate> with its attributes, as Translate does ([#292](#292)) ([243bbb0](243bbb0)) * move Vector's skin list into the appearance menu ([#423](#423)) ([68917e0](68917e0)), closes [#405](#405) * name the project namespace Wikven instead of clashing with MediaWiki ([#105](#105)) ([4695978](4695978)) * never read a translation file as a base page ([#390](#390)) ([6adb270](6adb270)) * obtain GadgetRepo from the service, not the removed singleton() ([288c733](288c733)) * point prevnext navigation at the reader's-language pages ([#245](#245)) ([a3c8411](a3c8411)) * probe openssl's own compiled-in CA bundle location first ([#309](#309)) ([c82d5b1](c82d5b1)) * propagate the child's exit code from wikven build and serve ([#302](#302)) ([5c75797](5c75797)) * read a source page the way Translate segments it ([#388](#388)) ([ff95548](ff95548)) * read a subpage as a translation only where it says it is one ([#580](#580)) ([e967fb5](e967fb5)) * read units the way Translate does ([#358](#358)) ([4642fc8](4642fc8)) * read Vector feature classes through classList ([#301](#301)) ([9dfebd1](9dfebd1)) * rebase the printfooter link on a page exported into a subdirectory ([#422](#422)) ([5e5b905](5e5b905)) * refuse an extension or skin name that points outside the image ([#570](#570)) ([35c0678](35c0678)) * refuse an image or asset path that climbs out of its directory ([#588](#588)) ([8fe34a1](8fe34a1)) * refuse images the source tree only points at ([#596](#596)) ([38669f5](38669f5)) * render every translated page after all pages are marked ([#243](#243)) ([0d987d3](0d987d3)) * reparent the local URLs a page carries in its JavaScript config ([#445](#445)) ([beba746](beba746)) * replace fetchExtensions' curl subprocess with core's HTTP client ([#308](#308)) ([9be62ec](9be62ec)) * replace hand-rolled balanced-div matcher with RemexHtml ([#314](#314)) ([3173c2d](3173c2d)) * report a vetoed File:/MediaWiki: import as a failure ([#291](#291)) ([8140261](8140261)) * require real tag context before rewriting a relative reference ([#313](#313)) ([0f04ab6](0f04ab6)) * require UniversalLanguageSelector explicitly and disable its webfonts ([#218](#218)) ([1b3e1b7](1b3e1b7)) * resolve $wgWikvenLogos through Title/RepoGroup, not by hand ([#315](#315)) ([6f4de14](6f4de14)) * resolve phan findings and target MediaWiki 1.45 ([be87a48](be87a48)) * resolve translate mark/stamp file paths against the source directory ([#220](#220)) ([0aa95ea](0aa95ea)) * retry and cache the build's Wikimedia Commons lookups ([#257](#257)) ([2ac2cce](2ac2cce)) * run the job queue in a fixed order ([#281](#281)) ([6aa694e](6aa694e)) * say what language the licenses page's copies are in ([#562](#562)) ([4242977](4242977)) * send a Special:MyLanguage link to the file it means ([#611](#611)) ([1aad459](1aad459)) * set the maintenance user without StubGlobalUser ([8128dc5](8128dc5)), closes [#93](#93) * settle the search bundle's language order between bakes ([#451](#451)) ([e7fc96c](e7fc96c)) * show the search box when SifterSearch provides static search ([#99](#99)) ([a9cd689](a9cd689)) * small build-script and config papercuts ([#76](#76)) ([1302e23](1302e23)) * split translation units only on adjacent newlines, as Translate does ([#295](#295)) ([0feeebf](0feeebf)) * stop a new unit inheriting a deleted one's translations ([#564](#564)) ([5fdd228](5fdd228)) * stop suppressing real categories; drop Version __NOINDEX__ ([#122](#122)) ([a3686bf](a3686bf)) * stop the build when an image of yours did not import ([#569](#569)) ([c78b572](c78b572)) * stop the RLPAGEMODULES rewrite from editing article prose ([#296](#296)) ([9ef6b8a](9ef6b8a)) * stop three things going quiet ([#599](#599)) ([9103f0c](9103f0c)) * stop ULS reaching for input methods the export cannot serve ([#402](#402)) ([af60e0e](af60e0e)) * strip the clocks ImageMagick writes into exported thumbnails ([#276](#276)) ([a1bb8d0](a1bb8d0)) * switch skins correctly from a translated page ([#258](#258)) ([6f85fe6](6f85fe6)) * take the DISPLAYTITLE spellings from the magic word registry ([#293](#293)) ([7c88a7a](7c88a7a)) * take the translations comment down rather than turn it into an all-clear ([#595](#595)) ([6b88a10](6b88a10)) * **tf:** skip the vulnerability-alerts import when the resource is absent ([#265](#265)) ([b5d3ba3](b5d3ba3)) * translate a page whose file name is not spelled like its title ([#567](#567)) ([6ec596a](6ec596a)) * turn the parser cache off so a bake reads its own finished state ([#335](#335)) ([91036c3](91036c3)) * use CSSMin's data: URI encoder in AssetLocalizer ([#310](#310)) ([6fe63d5](6fe63d5)) * validate .wikven.yaml value shapes and flag warnings clearly ([#163](#163)) ([021f819](021f819)) * wrap a word too long for the screen instead of scrolling the page ([#545](#545)) ([818a0e9](818a0e9)) ### Performance * freeze page_touched once, in the orchestrator ([#431](#431)) ([a2d878b](a2d878b)) * render the skins beside each other ([#437](#437)) ([33bbbfb](33bbbfb)) * stop rendering the history pages the export throws away ([#432](#432)) ([39fe30c](39fe30c)) ### Refactoring * group the composite actions under actions/ ([#359](#359)) ([01f0f3d](01f0f3d)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scribunto is ordinary equipment on a MediaWiki wiki, and wikven's two products disagreed about it silently. Measured on a bake of one page invoking one module, every one of these exited 0:
Module:ExampleLua says helloModule:Example{{#invoke:Greet|hello}}Module:Example.wikitext{{#invoke:Greet|hello}}Module%3AGreet.htmlThree of those four ways to have a Lua module published a site with braces where the page meant to say something, and one exported the module's own Lua source as a page. Nothing said a word. #465 guessed this would show up as "red module errors"; raw wikitext is worse, because it looks like someone's typo rather than a missing engine.
The answer, per the decision on #465
Supported under the Docker image, not in the standalone binary. That is not a preference. static-php-cli, which builds the binary's PHP, offers no Lua extension among the ~130 it supports, and Scribunto's other engine shells out to a
luaprogram that a single executable has no way to ship. Checked against upstream's own list rather than assumed.One refusal and one warning
includes/Scribunto.phpholds both rules;build.phpruns them beforeclearOutputDirectory, so a site that is refused still has its last bake on disk.Scribuntowhere no engine can run it → refused, naming the product that cannot. The site asked for Lua in so many words and this build cannot give it; publishing braces instead is worse than being told no.Module:files with noScribuntoinextensions→ warned about, naming the files, and the bake goes on. The site never asked for Lua, and what those files are is a guess read off a name: they may be on their way in, on their way out, or kept for something else. Refusing there decides for the site, which is not this build's call. So it says what it sees — the invocations stay in the pages as their own source text, and a module named with the.wikitextmarker is exported as a page — and lets the bake finish.A site with no Lua at all is told nothing, which matters: that is most sites, and the binary is what they are pointed at.
Verified by baking, not by reading. The refusal:
The engine-missing case was tested by mounting an empty ini over the image's
docker-php-ext-luasandbox.ini, which is as close to the binary as this machine gets:That run also proves the image has no
luaonPATH: engine detection triesluasandbox, then a configuredluaPath, thenlua5.1/luaonPATH, and it still fired. A hand-rolled install with the standalone engine is therefore not refused for no reason.The modules-without-Scribunto case was baked while it was still a refusal (
bake exit: 1, noindex.html, message namingModule:Greet). Since it became a warning it is covered by the unit suite and not by a fresh bake — the bake host here has no Docker. What changed is which of the two functions produces the string and whetherbuild.phpcallsfatalError()oroutput()with it; the message and the file detection are the same code that bake exercised.The page
Lua modules, between Extensions and JavaScript, in English and Korean. How to enable it, why a module file carries no.wikitextmarker (its content model is Lua), what reaches the published site, and what each of the two mismatches does.It demonstrates itself: it shows
Module:Examplein full, invokes it, and prints the answer. The module returns the title of the page it ran on and no prose, because a module's return value is not a translation unit and would reach a reader of any language in whatever language it was written in — the sentence around it carries the words instead.Standalone binarygains a "What it cannot do" section: it claimed its output was the same as the image's, which is now known to be false in exactly one way.Extensionspoints at the new page.Developmentdocuments the new build step.What holds it
smoke.ymlasserts the module's answer is in the exported page in both languages, and that nothing namedModule*was exported.It deliberately does not assert the absence of
{{#invoke:in the output. The new page documents the syntax inside<syntaxhighlight>and so contains it legitimately — confirmed in the bake, three occurrences. The positive assertion is the one that tells rendering from leaking.Ran locally against a real bake of this site:
Two bakes byte-identical, so a module costs no reproducibility. The prevnext chain reads Skins → Extensions → Lua modules → JavaScript → Searching, and shows
Lua 모듈in Korean, which incidentally exercises #466 on a brand-new page.translate checkreports everything up to date.A consequence worth stating plainly
This documentation site now has a Lua module, so the standalone binary cannot bake it. Nothing in CI notices, because nothing bakes anything with the binary:
binary.ymlbuilds it and runs./wikven version. That gap is real, it is not this change's to close, and it is worth its own issue.Closes #465.